--- /dev/null
+/* GSK - The GTK Scene Kit
+ *
+ * Copyright 2016 Endless
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:GskRoundedRect
+ * @Title: GskRoundedRect
+ * @Short_description: A rounded rectangle
+ *
+ * #GskRoundedRect defines a rectangle with rounded corners, as is commonly
+ * used in drawing.
+ *
+ * Operations on a #GskRoundedRect will normalize the rectangle, to
+ * ensure that the bounds are normalized and that the corner sizes don't exceed
+ * the size of the rectangle. The algorithm used for normalizing corner sizes
+ * is described in [the CSS specification](https://drafts.csswg.org/css-backgrounds-3/#border-radius).
+ */
+
+#include "config.h"
+
+#include "gskroundedrect.h"
+
+#include "gskdebugprivate.h"
+
+#include <math.h>
+
+static void
+gsk_rounded_rect_normalize_in_place (GskRoundedRect *self)
+{
+ float factor = 1.0;
+ float corners;
+ guint i;
+
+ graphene_rect_normalize (&self->bounds);
+
+ for (i = 0; i < 4; i++)
+ {
+ self->corner[i].width = MAX (self->corner[i].width, 0);
+ self->corner[i].height = MAX (self->corner[i].height, 0);
+ }
+
+ /* clamp border radius, following CSS specs */
+ corners = self->corner[GSK_CORNER_TOP_LEFT].width + self->corner[GSK_CORNER_TOP_RIGHT].width;
+ if (corners > self->bounds.size.width)
+ factor = MIN (factor, self->bounds.size.width / corners);
+
+ corners = self->corner[GSK_CORNER_TOP_RIGHT].height + self->corner[GSK_CORNER_BOTTOM_RIGHT].height;
+ if (corners > self->bounds.size.height)
+ factor = MIN (factor, self->bounds.size.height / corners);
+
+ corners = self->corner[GSK_CORNER_BOTTOM_RIGHT].width + self->corner[GSK_CORNER_BOTTOM_LEFT].width;
+ if (corners > self->bounds.size.width)
+ factor = MIN (factor, self->bounds.size.width / corners);
+
+ corners = self->corner[GSK_CORNER_TOP_LEFT].height + self->corner[GSK_CORNER_BOTTOM_LEFT].height;
+ if (corners > self->bounds.size.height)
+ factor = MIN (factor, self->bounds.size.height / corners);
+
+ for (i = 0; i < 4; i++)
+ graphene_size_scale (&self->corner[i], factor, &self->corner[i]);
+}
+
+/**
+ * gsk_rounded_rect_init:
+ * @self: The #GskRoundedRect to initialize
+ * @bounds: a #graphene_rect_t describing the bounds
+ * @top_left: the rounding radius of the top left corner
+ * @top_right: the rounding radius of the top right corner
+ * @bottom_right: the rounding radius of the bottom right corner
+ * @bottom_left: the rounding radius of the bottom left corner
+ *
+ * Initializes the given #GskRoundedRect with the given values.
+ *
+ * This function will implicitly normalize the #GskRoundedRect
+ * before returning.
+ *
+ * Returns: (transfer none): the initialized rectangle
+ *
+ * Since: 3.90
+ */
+GskRoundedRect *
+gsk_rounded_rect_init (GskRoundedRect *self,
+ const graphene_rect_t *bounds,
+ const graphene_size_t *top_left,
+ const graphene_size_t *top_right,
+ const graphene_size_t *bottom_right,
+ const graphene_size_t *bottom_left)
+{
+ graphene_rect_init_from_rect (&self->bounds, bounds);
+ graphene_size_init_from_size (&self->corner[GSK_CORNER_TOP_LEFT], top_left);
+ graphene_size_init_from_size (&self->corner[GSK_CORNER_TOP_RIGHT], top_right);
+ graphene_size_init_from_size (&self->corner[GSK_CORNER_BOTTOM_RIGHT], bottom_right);
+ graphene_size_init_from_size (&self->corner[GSK_CORNER_BOTTOM_LEFT], bottom_left);
+
+ gsk_rounded_rect_normalize_in_place (self);
+
+ return self;
+}
+
+/**
+ * gsk_rounded_rect_init_copy:
+ * @self: a #GskRoundedRect
+ * @src: a #GskRoundedRect
+ *
+ * Initializes @self using the given @src rectangle.
+ *
+ * This function will implicitly normalize the #GskRoundedRect
+ * before returning.
+ *
+ * Returns: (transfer none): the initialized rectangle
+ *
+ * Since: 3.90
+ */
+GskRoundedRect *
+gsk_rounded_rect_init_copy (GskRoundedRect *self,
+ const GskRoundedRect *src)
+{
+ *self = *src;
+
+ gsk_rounded_rect_normalize_in_place (self);
+
+ return self;
+}
+
+/**
+ * gsk_rounded_rect_init_from_rect:
+ * @self: a #GskRoundedRect
+ * @bounds: a #graphene_rect_t
+ * @radius: the border radius
+ *
+ * Initializes @self to the given @bounds and sets the radius of all
+ * four corners to @radius.
+ *
+ * Returns: (transfer none): the initialized rectangle
+ **/
+GskRoundedRect *
+gsk_rounded_rect_init_from_rect (GskRoundedRect *self,
+ const graphene_rect_t *bounds,
+ float radius)
+{
+ graphene_size_t corner = GRAPHENE_SIZE_INIT(radius, radius);
+
+ return gsk_rounded_rect_init (self, bounds, &corner, &corner, &corner, &corner);
+}
+
+/**
+ * gsk_rounded_rect_normalize:
+ * @self: a #GskRoundedRect
+ *
+ * Normalizes the passed rectangle.
+ *
+ * this function will ensure that the bounds of the rectanlge are normalized
+ * and ensure that the corner values are positive and the corners do not overlap.
+ *
+ * Returns: (transfer none): the normalized rectangle
+ *
+ * Since: 3.90
+ */
+GskRoundedRect *
+gsk_rounded_rect_normalize (GskRoundedRect *self)
+{
+ gsk_rounded_rect_normalize_in_place (self);
+
+ return self;
+}
+
+/**
+ * gsk_rounded_rect_offset:
+ * @self: a #GskRoundedRect
+ * @d_x: the horizontal offset
+ * @d_y: the vertical offset
+ *
+ * Offsets the bound's origin by @dx and @dy.
+ *
+ * The size and corners of the rectangle are unchanged.
+ *
+ * Returns: (transfer none): the offset rectangle
+ *
+ * Since: 3.90
+ */
+GskRoundedRect *
+gsk_rounded_rect_offset (GskRoundedRect *self,
+ float dx,
+ float dy)
+{
+ gsk_rounded_rect_normalize (self);
+
+ self->bounds.origin.x += dx;
+ self->bounds.origin.y += dy;
+
+ return self;
+}
+
+/**
+ * gsk_rounded_rect_is_rectilinear:
+ * @self: the #GskRoundedRect to check
+ *
+ * Checks if all corners of @self are right angles and the
+ * rectangle covers all of its bounds.
+ *
+ * This information can be used to decide if gsk_clip_node_new()
+ * or gsk_rounded_clip_node_new() should be called.
+ *
+ * Returns: %TRUE if the rectangle is rectilinear
+ **/
+gboolean
+gsk_rounded_rect_is_rectilinear (GskRoundedRect *self)
+{
+ guint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (self->corner[i].width > 0 ||
+ self->corner[i].height > 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
+{
+ if (negative)
+ cairo_arc_negative (cr, 0.0, 0.0, 1.0, angle1, angle2);
+ else
+ cairo_arc (cr, 0.0, 0.0, 1.0, angle1, angle2);
+}
+
+static void
+_cairo_ellipsis (cairo_t *cr,
+ double xc, double yc,
+ double xradius, double yradius,
+ double angle1, double angle2)
+{
+ cairo_matrix_t save;
+
+ if (xradius <= 0.0 || yradius <= 0.0)
+ {
+ cairo_line_to (cr, xc, yc);
+ return;
+ }
+
+ cairo_get_matrix (cr, &save);
+ cairo_translate (cr, xc, yc);
+ cairo_scale (cr, xradius, yradius);
+ append_arc (cr, angle1, angle2, FALSE);
+ cairo_set_matrix (cr, &save);
+}
+
+void
+gsk_rounded_rect_path (const GskRoundedRect *self,
+ cairo_t *cr)
+{
+ cairo_new_sub_path (cr);
+
+ _cairo_ellipsis (cr,
+ self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width,
+ self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height,
+ self->corner[GSK_CORNER_TOP_LEFT].width,
+ self->corner[GSK_CORNER_TOP_LEFT].height,
+ G_PI, 3 * G_PI_2);
+ _cairo_ellipsis (cr,
+ self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_TOP_RIGHT].width,
+ self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height,
+ self->corner[GSK_CORNER_TOP_RIGHT].width,
+ self->corner[GSK_CORNER_TOP_RIGHT].height,
+ - G_PI_2, 0);
+ _cairo_ellipsis (cr,
+ self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_BOTTOM_RIGHT].width,
+ self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_RIGHT].height,
+ self->corner[GSK_CORNER_BOTTOM_RIGHT].width,
+ self->corner[GSK_CORNER_BOTTOM_RIGHT].height,
+ 0, G_PI_2);
+ _cairo_ellipsis (cr,
+ self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width,
+ self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_LEFT].height,
+ self->corner[GSK_CORNER_BOTTOM_LEFT].width,
+ self->corner[GSK_CORNER_BOTTOM_LEFT].height,
+ G_PI_2, G_PI);
+
+ cairo_close_path (cr);
+}
+
--- /dev/null
+/* GSK - The GTK Scene Kit
+ *
+ * Copyright 2016 Endless
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_ROUNDED_RECT_H__
+#define __GSK_ROUNDED_RECT_H__
+
+#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
+#error "Only <gsk/gsk.h> can be included directly."
+#endif
+
+#include <gsk/gsktypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GSK_ROUNDED_RECT_INIT:
+ * @_x: the X coordinate of the origin
+ * @_y: the Y coordinate of the origin
+ * @_w: the width
+ * @_h: the height
+ *
+ * Initializes a #GskRoundedRect when declaring it.
+ *
+ * Since: 3.90
+ */
+#define GSK_ROUNDED_RECT_INIT(_x,_y,_w,_h) (GskRoundedRect) { .rect = GRAPHENE_RECT_INIT(_x,_y,_w,_h) }
+
+/**
+ * GskRoundedRect:
+ * @bounds: the bounds of the rectangle
+ * @corner: the size of the 4 rounded corners
+ *
+ * A rectanglular region with rounded corners.
+ *
+ * Application code should normalize rectangles using gsk_rounded_rect_normalize();
+ * this function will ensure that the bounds of the rectanlge are normalized
+ * and ensure that the corner values are positive and the corners do not overlap.
+ * All functions taking a #GskRoundedRect as an argument will internally operate on
+ * a normalized copy; all functions returning a #GskRoundedRect will always return
+ * a normalized one.
+ *
+ * Since: 3.90
+ */
+typedef struct _GskRoundedRect GskRoundedRect;
+
+struct _GskRoundedRect
+{
+ graphene_rect_t bounds;
+
+ graphene_size_t corner[4];
+};
+
+GDK_AVAILABLE_IN_3_90
+GskRoundedRect * gsk_rounded_rect_init (GskRoundedRect *self,
+ const graphene_rect_t *rect,
+ const graphene_size_t *top_left,
+ const graphene_size_t *top_right,
+ const graphene_size_t *bottom_right,
+ const graphene_size_t *bottom_left);
+GDK_AVAILABLE_IN_3_90
+GskRoundedRect * gsk_rounded_rect_init_copy (GskRoundedRect *self,
+ const GskRoundedRect *src);
+GDK_AVAILABLE_IN_3_90
+GskRoundedRect * gsk_rounded_rect_init_from_rect (GskRoundedRect *self,
+ const graphene_rect_t *bounds,
+ float radius);
+
+GDK_AVAILABLE_IN_3_90
+GskRoundedRect * gsk_rounded_rect_normalize (GskRoundedRect *self);
+
+GDK_AVAILABLE_IN_3_90
+GskRoundedRect * gsk_rounded_rect_offset (GskRoundedRect *self,
+ float dx,
+ float dy);
+
+GDK_AVAILABLE_IN_3_90
+gboolean gsk_rounded_rect_is_rectilinear (GskRoundedRect *self);
+
+G_END_DECLS
+
+#endif /* __GSK_ROUNDED_RECT_H__ */